﻿using System;
using System.Windows;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;

namespace mates_viz
{
    class UIVizualization
    {

		//public static readonly Color[] vizPalette = {
		//    Color.FromRgb(255,31,170),	// Pink
		//    Color.FromRgb(255,31,39),	// Red
		//    Color.FromRgb(255,114,31),	// Orange
		//    Color.FromRgb(255,165,31),	// Yellow
		//    Color.FromRgb(255,210,31),	
		//    Color.FromRgb(255,255,31),	// Green
		//    Color.FromRgb(203,249,20),
		//    Color.FromRgb(67,233,15),
		//    Color.FromRgb(15,176,233),	// Light Blue
		//    Color.FromRgb(15,107,233),	
		//    Color.FromRgb(32,15,233),	// Blue
		//    Color.FromRgb(141,15,233)	// Purple
		//};


		// test set

		//Color.FromRgb(0,0,143),
		//    Color.FromRgb(66,0,143),
		//    Color.FromRgb(0,60,0),
		//    Color.FromRgb(60,0,20),
		//    //Color.FromRgb(143,41,0),	
		//    //Color.FromRgb(143,77,0),	
		//    //Color.FromRgb(143,108,0),
		//    //Color.FromRgb(143,138,0),
		//    //Color.FromRgb(116,141,0),
		//    //Color.FromRgb(61,133,0),	
		//    Color.FromRgb(0,108,100),
		//    Color.FromRgb(0,65,133)	
		//    //Color.FromRgb(0,0,255),
		//    //Color.FromRgb(0,127,255),
		//    //Color.FromRgb(0,206,255)
		//    //Color.FromRgb(0,0,255),
		//    //Color.FromRgb(255,0,51),
		//    //Color.FromRgb(255,196,0),
		//    //Color.FromRgb(101,206,12)

		public static readonly Color[] vizPalette = {

		    Color.FromRgb(11,150,183),
		    Color.FromRgb(9,118,144),
		    Color.FromRgb(11,95,183),
		    Color.FromRgb(9,75,144),
		    Color.FromRgb(0,0,194),	
		    Color.FromRgb(0,0,153),	
		    Color.FromRgb(93,0,194),
		    Color.FromRgb(76,0,153),
		    Color.FromRgb(194,0,194),
		    Color.FromRgb(153,0,153),	
			Color.FromRgb(194,0,39),
		    Color.FromRgb(153,0,31),	
	
		};

		

		public struct VizAddr { public int ringIndex; public int tickIndex; }

        public int RotateStepCount = 10;
        public delegate void userEventDelegate(string username);

		public delegate void userMouseEventDelegate(string username, int loc_id, Point canvasPoint );
		// fired when the user scrolls over another user's circle
		public event userMouseEventDelegate userMouseOver;

        public void RaiseMouseOver(UIUser user, int loc_id, System.Windows.Input.MouseEventArgs mouseArgs ) 
		{
			Point hoverLoc = mouseArgs.GetPosition(vizCanvas);
			userMouseOver(user.Username, loc_id, hoverLoc); 
		}
        private stateManager stateMgr;  // a pointer to my state manager
        public stateManager StateManager { set { stateMgr = value; } get { return stateMgr; } }
        private Canvas vizCanvas;

		// Visual representation of the logged-in user & his/her location
		Ellipse myUserEllipse;
		Label myUserNameLabel;
		
		private SolidColorBrush userLocColor = new SolidColorBrush(Colors.Gray);

        private locationKey vizLocKey;
        LinkedList<UILocation> Locations = new LinkedList<UILocation>();    

        public void AddToCanvas(Canvas arcCanvas)
        {   // add the passed-in arc canvas to the viz canvas.
            vizCanvas.Children.Add(arcCanvas);
			// position the canvas in the center.
			Canvas.SetLeft(arcCanvas, vizCanvas.Width / 2 - 20);
			Canvas.SetTop(arcCanvas, vizCanvas.Height / 2 - 20);
			UpdateBrightnesses();
        }
        public void RemoveFromCanvas(Canvas arcCanvas)
        {   // remove the passed-in arc canvas from the viz canvas
            vizCanvas.Children.Remove(arcCanvas);
			UpdateBrightnesses();
        }
        public UIVizualization(Canvas VizCanvas, locationKey LocKey)
        {
            vizCanvas = VizCanvas;
            vizLocKey = LocKey;

			myUserEllipse = new Ellipse();
			myUserEllipse.RadiusX = 47; myUserEllipse.RadiusY = 47;
			myUserEllipse.CenterX =  vizCanvas.Width / 2;
			myUserEllipse.CenterY = vizCanvas.Height / 2;
			
			myUserEllipse.Fill = Brushes.Black;

			myUserNameLabel = new Label();
			myUserNameLabel.Width = 150;
			myUserNameLabel.Height = 40;
			myUserNameLabel.FontFamily = "Arial";
			myUserNameLabel.Content = "not logged in";
			myUserNameLabel.FontWeight = FontWeight.Bold;
			myUserNameLabel.Foreground = Brushes.White;
			myUserNameLabel.Background = Brushes.Transparent;
			myUserNameLabel.FontSize = FontSize.FromCentimeters(0.5);
			myUserNameLabel.HorizontalContentAlignment = HorizontalAlignment.Center;
			Canvas.SetLeft(myUserNameLabel, vizCanvas.Width / 2 - myUserNameLabel.Width / 2);
			Canvas.SetTop(myUserNameLabel, vizCanvas.Height / 2 - myUserNameLabel.Height / 2);

			// Add the elements to the grid.
			vizCanvas.Children.Add(myUserEllipse); 
			vizCanvas.Children.Add(myUserNameLabel);


		}


        public UILocation NextLoc(UILocation curLoc)
        {
            LinkedListNode<UILocation> curLocNode = Locations.Find(curLoc);
            if (curLocNode.Next == null)        // no more locations.  return null.
                return null;
            else
                return curLocNode.Next.Value;   // return the next arc in this loc.
        }
        public UILocation PreviousLoc(UILocation curLoc)
        {
            LinkedListNode<UILocation> curLocNode = Locations.Find(curLoc);
            if (curLocNode.Previous == null)        // no previous locs.  return null.
                return null;
            else
                return curLocNode.Previous.Value;   // return the previous arc in this loc.
        }
        /// <summary>
        /// This function attempts to find the UILocation with specified loc_id.  If it can't find it,
        /// It will insert one (in order) and return it.
        /// </summary>
        /// <param name="loc_id">Location's Mates Identifier</param>
        /// <param name="InsertIfMissing">If true, a UI location will be created if it cannot be found.</param>
        private UILocation findLocation(int loc_id, bool InsertIfMissing)
        {
            double newLocDistance = 0;
            if (InsertIfMissing)
            {
                stateManager.matesLoc userLoc = stateMgr.GetLocation(loc_id);
                newLocDistance = userLoc.Distance;      // get the distance.
            }
           
            UILocation newUserLoc = null;
            // look through all current locations and find where the new user needs to go.
            // if a location doesn't exist, create it (but as of now, it will have size 0).

            LinkedListNode<UILocation> locNode = Locations.First;
            while(locNode != null)
            {
                newUserLoc = locNode.Value;
                if (locNode.Value.Loc_ID == loc_id) break;  // found the location.  use it.
                if (InsertIfMissing)                        // if requested to, find where to insert
                {
                    if (locNode.Value.Distance > newLocDistance)  // found a loc that's further than me
                    {   // I need to go immediately before it.
                        newUserLoc = new UILocation(this, loc_id);      // create a new one.  add it.
                        Locations.AddBefore(locNode, new LinkedListNode<UILocation>(newUserLoc));
                        break;
                    }
                }

                locNode = locNode.Next; // go to the next item in the linked list
            }

            if (locNode == null)     // if the loc node is null, we didn't find a place to insert.
            {
                if (InsertIfMissing)    // if we're asked to insert something, and we didn't find anything
                {                       // then add the location to the end.
                    newUserLoc = new UILocation(this, loc_id);
                    Locations.AddLast(new LinkedListNode<UILocation>(newUserLoc));
                }
                else return null;   // we didn't find anything and aren't supposed to insert anything.
            }

            return newUserLoc;
        }
        public void RemoveUser(string username)
        {
            // ask the state manager for info on the user's location            
            stateManager.matesUser user = stateMgr.GetUser(username);
            if(user != null) this.RemoveUser(username, user.Loc_ID);
        }
        public void AddUser(string username)
        {
            // ask the state manager for info on the user's location            
            stateManager.matesUser user = stateMgr.GetUser(username);
            AddUser(username, user.Loc_ID); // and add the user to that loc.
        }
        public void AddLocationToKey(UILocation UIloc)
        {
            // ask the state manager for info on the user's location            
            stateManager.matesLoc loc = stateMgr.GetLocation(UIloc.Loc_ID);
			vizLocKey.addLoc(loc.ID, loc.Name, UIloc.Color);
			if (UIloc.Loc_ID == stateMgr.MyLocID)
			{
				myUserEllipse.Fill = new SolidColorBrush(UIloc.Color); 
				myUserNameLabel.Content = stateMgr.MyUserName;
			}
        }

		public void RemoveLocationFromKey(UILocation UILoc) { vizLocKey.removeLoc(UILoc.Loc_ID); }

		public VizAddr FindInsertionPoint(UILocation newLoc)
		{
			VizAddr insert;			// stores where to insert the new loc.

			insert.tickIndex = 0;	// starting position of the arc.
			insert.ringIndex = 1;	// starting ring for the arc.
			UILocation prevLoc = PreviousLoc(newLoc);
			if (prevLoc != null)
			{   // find the end tick of the next loc
				if (prevLoc.FirstArc != null)
				{
					insert.tickIndex = prevLoc.LastArc.EndTick;
					insert.ringIndex = prevLoc.LastArc.RingIndex;
					// if we need to be created on the next outermost ring, do so now.
					if (prevLoc.LastArc.FillsRing) { insert.ringIndex++; insert.tickIndex = 0; }
				}
			}
			return insert;
		}


		private void UpdateBrightnesses()
		{
			return;

			LinkedListNode<UILocation> locNode = Locations.First;

			int i = Locations.Count;
			while (locNode != null)
			{
				locNode.Value.UpdateBrightness(0.25 + 0.75 *((double)i / Locations.Count)); i--;
				locNode = locNode.Next; // go to the next item in the linked list
			}

		}

		public int findSafeColor(UILocation newLoc)
		{
			// First, determine where the new location will be created.
			// (what ring and what tick index)

			// determine where this location will be inserted into the viz
			VizAddr iPoint = FindInsertionPoint(newLoc);

			double[] colorDist = new double[vizPalette.Length];
			// initialize to a "big" number so the unused colors have the highest distance
			for (int i = 0; i < vizPalette.Length; i++) colorDist[i] = 1000;
			
			LinkedListNode<UILocation> locNode = Locations.First;
			while (locNode != null)
			{
				// don't compare color against ourself
				if (locNode.Value != newLoc)
				{
					int colorIdx = locNode.Value.ColorIndex;
					// first, compute the ring difference

					colorDist[colorIdx] = Math.Min(
						minDistance(iPoint.ringIndex, iPoint.tickIndex,
						  locNode.Value.FirstArc.RingIndex,
						  locNode.Value.FirstArc.StartTick,
						  locNode.Value.FirstArc.EndTick),
						minDistance(iPoint.ringIndex, iPoint.tickIndex,
						  locNode.Value.LastArc.RingIndex,
						  locNode.Value.LastArc.StartTick,
						  locNode.Value.LastArc.EndTick));

				}
				locNode = locNode.Next; // go to the next item in the linked list
			}

			// Search the array and find the furthest away color
			double maxDist = colorDist[0]; int maxIndex = 0;
			for (int i = 0; i < vizPalette.Length; i++)	{ if (colorDist[i] > maxDist) { maxDist = colorDist[i]; maxIndex = i; }}
			return maxIndex;

		}

		private double minDistance(int Arc1Ring, int Arc1Tick, int Arc2Ring, int Arc2StartTick, int Arc2EndTick)
		{
			double dist = Math.Abs(Arc1Ring - Arc2Ring);
			UIArc.Ring ring1 = new UIArc.Ring(Arc1Ring); UIArc.Ring ring2 = new UIArc.Ring(Arc2Ring);

			if (dist == 1)	// if the two arcs are on adjacent rings.
			{
				double ang1 = 360 - ring1.angleOf(Arc1Tick);
				// make the distance 0 if the arcs touch within their span
				if (ang1 >= ring2.angleOf(Arc2StartTick) && ang1 <= ring2.angleOf(Arc2EndTick)) dist = 0;
				else // if they don't touch, make the distance equal to  1 + the minimum angular distance
					dist = 1 + Math.Min(angDist(ang1, ring2.angleOf(Arc2StartTick)), ring2.angleOf(Arc2EndTick)) / 360;
			}
			else  // they're on either the same ring or they're separated by one or more rings
			{
				double ang1 = ring1.angleOf(Arc1Tick);
				if(ring1.CCW != ring2.CCW) ang1 = 360 - ang1;	// switch angle coordinate system if needed

				// add the minimum angular distance (as a fraction 0...1) to each distance
				dist = dist + Math.Min(angDist(ang1, ring2.angleOf(Arc2StartTick)), ring2.angleOf(Arc2EndTick)) / 360;
			}

			return dist;
		}

		/// <summary>
		/// Computes the minimum angular distance between two angles.
		/// Assumes the angles are in the range 0...2*Pi
		/// </summary>
		/// <param name="a1">First angle (0...2Pi)</param>
		/// <param name="a2">Second angle (0...2Pi)</param>
		/// <returns></returns>
		private double angDist(double a1, double a2)
		{
			// error checking.  can be removed later.
			if (a1 < 0 || a1 > 360 * 2 || a2 < 0 || a2 > 360) Error.Log("angDist: angles were out of range!");

			if (a1 > a2)
				return Math.Min(a1 - a2, (a2 + 360) - a1);
			else
				return Math.Min(a2 - a1, (a1 + 360) - a2);
		}




        private void AddUser(string username, int loc_id)
        {
            UILocation userLoc = findLocation(loc_id, true);  // figure out in which loc the user goes
            userLoc.AddUser(username);                        // add the user to that loc
        }

		public void HighlightLoc(int loc_id, bool on)
		{
			UILocation userLoc = findLocation(loc_id, false);
			if (userLoc != null)
				userLoc.HighlightSelf(on);
		}


        private void HighlightUser(string username, int loc_id, bool on, string highlight_type) 
        {
            UILocation userLoc = findLocation(loc_id, false);
            if (userLoc != null)
                userLoc.HighlightUser(username, on, highlight_type);
        }

        public void HighlightUser(string username, bool on, string highlight_type)
        {
            // ask the state manager for info on the user's location            
            stateManager.matesUser user = stateMgr.GetUser(username);
            if (user == null) Error.Log("Could not find user: " + username + " to highlight.");
            else HighlightUser(username, user.Loc_ID, on, highlight_type);
        }
        public void RemoveLoc(UILocation toRemove)
        {

			try
			{ RemoveLocationFromKey(toRemove); }
			catch { Error.Log(" UILoc: I could not find the loc I was asked to remove from the key!"); }

			try
            { Locations.Remove(toRemove); }
            catch { Error.Log(" UILoc: I could not find the loc I was asked to remove from the list!"); }

        }
        public void RemoveUser(string username, int loc_id)
        {
            // find the user's location ...
            UILocation userLoc = findLocation(loc_id, false);
            if (userLoc == null)
            { Error.Log("Could not find location " + loc_id + " to delete user " + username + " from!"); return; }

            // then tell that location to remove the user.
            userLoc.RemoveUser(username);
        }
    }
}